/**
 * Copyright (C) 2008 Christopher Allen Ogden
 *
 *     This file is part of Odysi.
 *
 *  Odysi is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 *
 *  Odysi is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with Odysi.  If not, see <http://www.gnu.org/licenses/>.
 */

#include <iostream>
#include <vector>
#include "socket.h"
#include "packetHeaders.h"
#include "genPackets.h"
#include "luaState.h"
#include "serverLua.h"
#include "log.h"
#include "client.h"
#include "luaAux.h"
#include "map.h"
#include "animationListManager.h"
#include "util.h"
#include <errno.h>

#define QUEUE_SIZE 5
#define INIT_SCRIPT "/scripts/server.init.lua"
#define CONFIG_VAL_PORT "port"

using namespace std;

namespace {
    static const std::string infoRunningInit("Running init script...");
    static const std::string infoListening("Listening for connections...");

    static const std::string errorServerSocket("Could not create server socket");
    static const std::string errorAccept("Could not accept connection");
    static const std::string errorPortNumber("Invalid port number.");

    static const std::string connectionClosed("Connection closed.");

    /**
     * Loads all the tiles.
     */
    void loadTiles(void)
    {
        TileManager::GS().load(LEVEL_BASE, PKGDATADIR FILE_TILEMAP_BASE);
        TileManager::GS().load(LEVEL_OBJECTS, PKGDATADIR FILE_TILEMAP_OBJECTS);
        TileManager::GS().load(LEVEL_CHARACTERS, PKGDATADIR FILE_TILEMAP_CHARACTERS);
    }
}

/**
 * Main entry point.
 *
 * @param argc  The number of arguments.
 * @param argv  The arguments in an array.
 */
int main(int argc, char **argv)
{
    LuaState L(luaL_newstate());
    luaL_openlibs(L);
    openOdysisLib(L);
    Lua::setPath(L);

    // Load configuration file.
    Log::GS().info(infoRunningInit);
    if (luaL_loadfile(L, PKGDATADIR INIT_SCRIPT) || lua_pcall(L, 0, 0, 0)) {
        throw std::runtime_error(lua_tostring(L, -1));
    }

    // Load the map.
    Map map(PKGDATADIR FILE_MAP);
    Lua::registerPointer(L, LUA_SR_MAP, reinterpret_cast<void *>(&map));
    
    loadTiles();
    AnimationListManager::GS().load(PKGDATADIR FILE_CHARACTERS);

    // Create the server socket.
    Socket serverSocket;
    if (serverSocket == SOCKET_ERROR) {
        perror(errorServerSocket.c_str());
        return 1;
    }

    // Set the reuse address option.
    serverSocket.setReuseAddress();

    // Set the server socket to non-blocking.
    serverSocket.setNonblocking();

    // Bind to port.
    lua_getglobal(L, CONFIG_VAL_PORT);
    if (!lua_isnumber(L, -1)) {
        throw std::runtime_error(errorPortNumber);
    }
    serverSocket.bind(lua_tointeger(L, -1));

    // Start listening.
    serverSocket.listen(QUEUE_SIZE);
    Log::GS().info(infoListening);

    // Create a vector of clients.
    vector<Client> clients;
    Lua::registerPointer(L, LUA_SR_CLIENTS, reinterpret_cast<void *>(&clients));

    clock_t nextTick = getTicks() + TICK_INTERVAL;

    // Enter main loop.
    while(true) {
        // Accept all connection.
        while (true) {
            Socket sock(serverSocket.accept());
            if (sock == SOCKET_ERROR && errno != EWOULDBLOCK) {
                perror(errorAccept.c_str());
                return 1;
            } else if (sock == SOCKET_ERROR) {
                break;
            }
            clients.push_back(Client(sock, L));
            sock.setNonblocking();
        }

        while (getTicks() >= nextTick) {
            nextTick += TICK_INTERVAL;
            map.tick();
        }

        // Now loop through the clients.
        for (vector<Client>::iterator iter = clients.begin(); iter != clients.end(); iter++) {
            try {
                (*iter).process();
            } catch (std::runtime_error &e) {
                if (!strcmp(e.what(), connectionClosed.c_str())) {
                    vector<Client>::iterator nextIter = iter;
                    nextIter--;
                    clients.erase(iter);
                    iter = nextIter;
                } else {
                    throw e;
                }
            }
        }
    }
}
